use std::io;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use dashmap::DashMap;
use http::{StatusCode, Uri};
use snafu::prelude::*;
use miette::{Diagnostic, SourceSpan};
use volo_http::context::ServerContext;
use volo_http::request::ServerRequest;
use volo_http::response::ServerResponse;
use volo_http::server::layer::TimeoutLayer;
use volo_http::server::middleware::Next;
use volo_http::server::{middleware, IntoResponse};
use volo_http::{cookie, http::header, Address, Router, Server, Json};
#[global_allocator]
static GLOBAL: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;


#[derive(Snafu, Debug, Diagnostic)]
pub enum MyLibError {
    #[snafu(display("Unable to read configuration from {}", path.display()))]
    #[diagnostic(code(my_lib::io_error), help("读取配置失败"))]
    ReadConfiguration { source: io::Error, path: PathBuf },

    #[snafu(display("Unable to write result to {}", path.display()))]
    #[diagnostic(code(my_lib::io_error), help("写入失败"))]
    WriteResult { source: io::Error, path: PathBuf },

    #[snafu(display("not found, {}", uri))]
    #[diagnostic(code("404"), help("not found"))]
    NotFound{source: io::Error, uri: String},

    #[snafu(display("bad request, {}", uri))]
    #[diagnostic(code("400"), help("bad request"))]
    BadRequest{source:io::Error, uri:String},
}

#[derive(Snafu, Diagnostic, Debug)]
pub struct AnotherError {
    #[label("here")]
    pub at: SourceSpan
}

impl IntoResponse for MyLibError {
    fn into_response(self) -> ServerResponse {
        match self {
            MyLibError::NotFound{..} => (StatusCode::NOT_FOUND, self.to_string()).into_response(),
            MyLibError::BadRequest{..} => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),
            _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
        }
    }
}
/*
Now let's define a function!

Use this `Result` type (or its expanded version) as the return type
throughout your app (but NOT your libraries! Those should always return
concrete types!).
*/
use miette::{Result};
use serde::{Deserialize, Serialize};
use snafu::Whatever;
use volo_http::body::Body;
use volo_http::cookie::CookieJar;
use volo_http::server::route::{get, post};

fn this_fails() -> Result<()> {
    // You can use plain strings as a `Source`, or anything that implements
    // the one-method `Source` trait.
    let path = "config.toml";
    fn unpack_config(_: &str) -> &str {
        "/some/path/that/does/not/exist"
    }
    let configuration = std::fs::read_to_string(path).context(ReadConfigurationSnafu { path })?;
    let path = unpack_config(&configuration);
    std::fs::write(&path, b"My complex calculation").context(WriteResultSnafu { path })?;
    Ok(())
}

/*
Now to get everything printed nicely, just return a `Result<()>`
and you're all set!

Note: You can swap out the default reporter for a custom one using
`miette::set_hook()`
*/
fn pretend_this_is_main() -> Result<()> {
    // kaboom~
    this_fails()?;

    Ok(())
}

#[derive(Debug, Clone)]
struct AFoo {
    map: Arc<DashMap<String, String>>
}

impl AFoo {
    fn new () -> Self {
        Self {
            map: Arc::new(DashMap::new())
        }
    }
}
// You can use the following commands for testing cookies
//
// ```bash
// # create a cookie jar for `curl`
// TMPFILE=$(mktemp --tmpdir cookie_jar.XXXXXX)
//
// # access it for more than one times!
// curl -v http://127.0.0.1:8080/ -b $TMPFILE -c $TMPFILE
// curl -v http://127.0.0.1:8080/ -b $TMPFILE -c $TMPFILE
// # ......
// ```
async fn tracing_from_fn(
    uri: Uri,
    peer: Address,
    cookie_jar: CookieJar,
    cx: &mut ServerContext,
    req: ServerRequest,
    next: Next,
) -> ServerResponse {
    tracing::info!("{:?}", *cookie_jar);
    let count = cookie_jar.get("count").map_or(0usize, |val| {
        val.value().to_string().parse().unwrap_or(0usize)
    });
    let start = std::time::Instant::now();
    let resp = next.run(cx, req).await;
    let elapsed = start.elapsed();

    tracing::info!("seq: {count}: {peer} request {uri}, cost {elapsed:?}");

    (
        (
            header::SET_COOKIE,
            cookie::Cookie::build(("count", format!("{}", count + 1)))
                .path("/")
                .max_age(cookie::Duration::days(1))
                .build()
                .to_string(),
        ),
        resp,
    )
        .into_response()
}
async fn headers_map_response(response: ServerResponse) -> impl IntoResponse {
    (
        [
            ("Access-Control-Allow-Origin", "*"),
            ("Access-Control-Allow-Headers", "*"),
            ("Access-Control-Allow-Method", "*"),
        ],
        response,
    )
}
type HandlerResponse = Result<ServerResponse, MyLibError>;
pub async fn create_user(
    // this argument tells axum to parse the request body
    // as JSON into a `CreateUser` type
    Json(payload): Json<CreateUser>,
) -> HandlerResponse {
    // insert your application logic here
    let user = User {
        id: 1337,
        username: payload.username,
    };

    // this will be converted into a JSON response
    // with a status code of `201 Created`
    Ok((StatusCode::CREATED, Json(user)).into_response())
}

// the input to our `create_user` handler
#[derive(Deserialize)]
pub struct CreateUser {
    username: String,
}

// the output to our `create_user` handler
#[derive(Serialize)]
pub struct User {
    id: u64,
    username: String,
}
pub async fn root() -> HandlerResponse {
    Ok(Body::from("Hello, World!").into_response())
}

pub fn merge_routers() -> Router {
    Router::new()
        .merge(new_router().route("/", get(root)))
        .merge(new_router().route("/users", post(create_user)))
}
pub(crate) fn new_router() -> Router {
    Router::new()
}

fn timeout_handler(_: &ServerContext) -> StatusCode {
    tracing::error!("timeout");
    StatusCode::REQUEST_TIMEOUT
}


#[tokio::main]
async fn main() -> std::io::Result<()> {
    color_backtrace::install();
    let subscriber = tracing_subscriber::FmtSubscriber::builder()
        .with_max_level(tracing::Level::TRACE)
        .finish();

    tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
    let app = Router::new()
        .merge(merge_routers())
        .layer(middleware::from_fn(tracing_from_fn))
        .layer(middleware::map_response(headers_map_response))
        .layer(TimeoutLayer::new(Duration::from_secs(5), timeout_handler));

    let addr: SocketAddr = "[::]:8080".parse().unwrap();
    let addr = Address::from(addr);

    println!("Listening on {addr}");

    let _ = Server::new(app).run(addr).await;
    Ok(())
}

#[cfg(test)]
mod tests {
    use ctor::ctor;
    use crate::AFoo;
    use httpmock::prelude::*;

    #[ctor]
    fn init_color_backtrace() {
        color_backtrace::install();
    }

    #[test]
    fn test1() {
        let m = AFoo::new();
        m.map.insert("xxx".into(), "kkk".into());
        println!("{:?}", m);
    }
    
    #[test]
    fn test2() {

        // 启动轻量级模拟服务器。
        let server = MockServer::start();

        // 在服务器上创建一个模拟。
        let mock = server.mock(|when, then| {
            when.method(GET)
                .path("/translate")
                .query_param("word", "hello");
            then.status(200)
                .header("content-type", "text/html; charset=UTF-8")
                .body("Привет");
        });
    }
}